/**
 * anji-allways.com Inc.
 * Copyright (c) 2016-2017 All Rights Reserved.
 */
package com.brighton.myspring.core;

import com.brighton.myspring.annotations.MyAutowired;
import com.brighton.myspring.annotations.MyService;
import com.brighton.myspring.annotations.MyTransactional;
import com.brighton.myspring.factory.ProxyFactory;

import java.io.File;
import java.io.FileFilter;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

/**
 * <p>
 * 容器 + 注解解析器
 * </p>
 *
 * @author Brighon
 * @version : 00:00 2020-09-15 Brighon Exp $
 */
public class MyApplicationContext {
    // 包名
    private String packageName;
    // 自定义的beans Map 单例的缓存池
    private ConcurrentHashMap<String, Object> beans = new ConcurrentHashMap<>();

    // 构造方法
    public MyApplicationContext(String packageName) {
        this.packageName = packageName;
        initBeans();
    }

    // 初始化并
    private void initBeans() {
        // 获取包下所有类的集合
        List<Class> classList = getClasses(packageName);
        // 找到添加了注解的类
        findClassIsAddedMyAnnotation(classList);
    }

    /**
     * @Description 获取包下所有类的集合
     * @author wangxiaoliang
     * @date 2020-09-15 01:08
     */
    private List<Class> getClasses(String packageName) {
        List<Class> classList = new ArrayList<>();
        String packageDirName = packageName.replace(".", "/");
        Enumeration<URL> dirs;
        try {
            dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
            while (dirs.hasMoreElements()) {
                URL url = dirs.nextElement();
                // 获取文件协议
                String protocol = url.getProtocol();
                if (protocol.equals("file")) {
                    String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
                    findAndAddClassesInPackageByFile(packageName, filePath, classList);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return classList;
    }

    /**
     * @Description 递归 查询包下所有的类
     * @author wangxiaoliang
     * @date 2020-09-15 01:08
     */
    private void findAndAddClassesInPackageByFile(String packageName, String filePath, List<Class> classList) {
        File dir = new File(filePath);
        // 选出文件夹下面所有的文件
        File[] files = dir.listFiles(new FileFilter() {
            @Override
            public boolean accept(File file) {
                // 判断是不是一个文件夹或者是不是以".class"结尾
                return (file.isDirectory() || file.getName().endsWith(".class"));
            }
        });
        for (File file : files) {
            if (file.isDirectory()) {
                // 如果是文件夹递归该文件
                findAndAddClassesInPackageByFile(packageName + "." + file.getName(), file.getAbsolutePath(), classList);
            } else {
                // 如果不是文件夹, 获取文件名
                String className = file.getName().substring(0, file.getName().length() - 6);
                try {
                    // 通过文件路径获取类, 加入集合中
                    classList.add(Class.forName(packageName + "." + className));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * @Description 查询所有添加了注解的类
     * @author wangxiaoliang
     * @date 2020-09-15 01:10
     */
    private void findClassIsAddedMyAnnotation(List<Class> classList) {
        try {
            for (Class aClass : classList) {
                MyService annotation = (MyService) aClass.getAnnotation(MyService.class);
                if (annotation != null) {
                    // 有MyService注解的类加入容器中
                    classToObjectIntoBeans(annotation, aClass, classList);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * @Description 将类加入beans中 也就是将类交给自定义的spring ApplicationContext容器
     * @author wangxiaoliang
     * @date 2020-09-16 17:18
     */
    private void classToObjectIntoBeans(MyService annotation, Class aClass, List<Class> classList) {
        Object obj = null;

        try {
            if (aClass.isInterface()) {
                // 如果当前类是接口 找到他的实现类 并创建这个类
                for (Class implClass : classList) {
                    // 遍历所有的类
                    if (implClass.isInterface()) {
                        // 如果还是接口跳过
                        continue;
                    }
                    // 判断implClass是不是aClass的实现类
                    Class fieldClassCopy = implClass.getClassLoader().loadClass(aClass.getName());
                    if (fieldClassCopy.isAssignableFrom(implClass)) {
                        // implClass是aClass的实现类
                        Constructor[] constructors = implClass.getConstructors();
                        for (Constructor constructor : constructors) {
                            // 参数数量
                            int parameterCount = constructor.getParameterCount();
                            if (parameterCount == 0) {
                                // 无参构造方法
                                obj = constructor.newInstance();
                            }
                        }
                        break;
                    }
                }
            } else {
                // 当前类不是接口 直接创建类
                Constructor[] constructors = aClass.getConstructors();
                for (Constructor constructor : constructors) {
                    int parameterCount = constructor.getParameterCount();
                    if (parameterCount == 0) {
                        obj = constructor.newInstance();
                    }
                }
            }
            if (obj != null) {
                // 类创建成功, 放入beans中
                if (annotation.value() != null && !annotation.value().equals("")) {
                    beans.put(annotation.value(), obj);
                } else {
                    beans.put(aClass.getSimpleName(), obj);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 从beans中获取bean
    public Object getBean(String beanName, MyApplicationContext myApplicationContext) {
        Object beanObject = beans.get(beanName);
        try {
            // 给其中注入(Autowired)的类赋值
            referenceBindObject(beanObject);
        } catch (Exception e) {
            e.printStackTrace();
        }

        Class aClass = beanObject.getClass();
        Annotation annotation = aClass.getAnnotation(MyTransactional.class);
        if (annotation != null && !aClass.isInterface()) {
            // 有事务注解且不是接口 获取动态代理的bean工厂
            ProxyFactory proxyFactory = (ProxyFactory) myApplicationContext.getBean("ProxyFactory", myApplicationContext);
            beanObject = proxyFactory.getJdkProxy(beanObject);
        } else if (annotation != null && aClass.isInterface()) {
            // 有事务注解且是接口 获取动态代理的bean工厂
            ProxyFactory proxyFactory = (ProxyFactory) myApplicationContext.getBean("ProxyFactory", myApplicationContext);
            beanObject = proxyFactory.getCglibProxy(beanObject);
        }
        return beanObject;
    }

    // 引用绑定的对象
    private Object referenceBindObject(Object beanObject) {
        Class beanClass = beanObject.getClass();
        Field[] declaredFields = beanClass.getDeclaredFields();
        try {
            for (Field field : declaredFields) {
                if (!field.isAccessible()) {
                    field.setAccessible(true);
                }
                // 检查参数上是否有Autowired注解
                MyAutowired filedAnnotation = field.getAnnotation(MyAutowired.class);
                if (filedAnnotation == null) {
                    System.out.println(beanClass.getName() + "类中的" + field.getName() + "字段,该字段上没有加注解");
                    break;
                }
                // 该参数上有Autowired注解  从beans中获取该类, 并递归, 获取该类中的Autowired的参数字段
                Class fieldClass = field.getType();
                String classSimpleName = fieldClass.getSimpleName();
                Object fieldObject = null;
                if (!filedAnnotation.value().equals("")) {
                    fieldObject = beans.get(filedAnnotation.value());
                } else {
                    fieldObject = beans.get(classSimpleName);
                }
                Object object = referenceBindObject(fieldObject);
                // 将该类赋值到这个bean中
                field.set(beanObject, object);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return beanObject;
    }

}
